Android 15新特性,强制edge-to-edge全面屏体验
大家好,今天原创。
Android 15再过不了多久就要正式发布了,我也是第一时间就去了解了今年新系统的一些变化。
怎么说呢?之前我们一直吐槽Android技术变化过快,新学的很多知识没过多久就废弃了,再加上Google又不断地推出更新的技术,搞得开发者经常直呼学不动了。。
而现在大家不用再担心这个问题了,因为Android 15可以说是一个非常小的版本更新,并没有太多非常亮眼的点拿出来介绍。
今年的Google I/O我也是观看了一遍,可以说现在AI成了Google唯一的焦点,Android已经登不了Google I/O的大舞台了,只能在一个小房间来介绍介绍Android 15的新特性。
Android技术慢了下来,热度也降了下来,是喜是忧,大家冷暖自知。
关于今年Android 15新特性的文章,我把主要的行为变更过了好几遍,感觉非常值得一讲的也就是强制edge-to-edge全面屏体验这项变化了。这项变化内容很简单,但是由于可能会影响到所有的Android应用,所以还是建议大家学习了解一下。
其实edge-to-edge全面屏体验并不是什么全新的功能,早在Android 15之前就已经支持了。
但是这个功能推出了很多年,仍然有大量的应用程序没有对全面屏体验进行适配。所以,在这次的Android 15更新中,Google终于下决心要强推这个功能,以让所有应用程序都能达到更好的体验。
需要说明的是,只有将App的targetSdkVersion指定到35或更高时,Android 15才会强制启用edge-to-edge功能。所以,如果你就是不想适配,那么只要不升级targetSdkVersion的版本就行了。
下面开始进入正题,首先跟大家介绍一下到底什么是edge-to-edge全面屏体验。
其实简单来讲,就是让App的界面延伸到手机屏幕的全部空间,这样可以带来更加沉浸式的用户体验。
事实上,绝大多数的App都没有将界面延伸到手机屏幕的全部空间,因此它们本可以提供更好的用户体验。
这里说的手机屏幕的全部空间具体指的是什么呢?我们看下面这张图就能快速了解了。
绝大部分的App其实都只使用了绿色这部分的空间,屏幕上方的状态栏以及屏幕下方的导航栏这两个白色部分的空间都是没有利用起来的,想想你写的App是不是也是这样?
而edge-to-edge全面屏体验就是要将所有的空间都利用起来,如下图所示。
从上面的两张图对比来看,或许有些朋友并不会觉得edge-to-edge效果带来了多少用户体验的提升。其实这个主要还是得看App的界面设计是什么样的,不同的界面对应出来的edge-to-edge效果也是完全不同的。
https://github.com/guolindev/Edge2EdgeTest
需要注意的是,一开始我将项目的targetSdkVersion指定的是34,也就是默认不会强制启用edge-to-edge功能。
现在将项目运行到Android 15设备上,效果如下图所示。
这个照片墙效果当然不能说是差,只能说中规中矩。毕竟在没有edge-to-edge全面屏体验之前,所有的App都只能做到这个效果。
那么现在有了edge-to-edge功能之后呢?我们来对比看看效果吧。让你的App支持edge-to-edge非常简单,只需要将targetSdkVersion指定成35或更高,就会自动变成edge-to-edge全面屏效果了,如下图所示。
怎么样,这样一对比,照片墙的沉浸感体验是不是立马就上来了?而且edge-to-edge效果看久了之后,你就再也回不去之前的UI效果了。
另外再说一些关于edge-to-edge全面屏体验的细节。当我们在照片墙上进行滚动时,你会发现屏幕底部导航条的颜色会随着滚动而发生变化。
这个是Android系统自带的功能,为了保证在启用了edge-to-edge全面屏体验之后,底部导航条不会因为因为背景的原因而难以识别。
发现了这个现象之后,可能细心的朋友立马就察觉到了,那如果我手机底部的导航栏模式不是这种手势导航栏,而是传统的Back、Home、Task 3按键导航栏,edge-to-edge全面屏体验会变成什么样呢?
这个结果估计大多数人都猜不到,我们直接来看效果截图吧。
可以看到,导航栏变成了一种半透明的效果,不透明度默认是80%。
从这个效果上我们也可以看出,3按键导航栏在edge-to-edge全面屏体验方面是完全落后的,这种模式后面就会逐渐被Android系统边缘化了。
同时被边缘化的还有一些与状态栏、导航栏颜色设置相关的API,这些API由于和edge-to-edge全面屏体验是相冲突的,有些是现在就已经不能用了,有些是已经不再推荐使用,反正大家看完这篇文章之后尽量就别再使用下面这些API了:
Window#setStatusBarColor
Window#setStatusBarContrastEnforced
Window#setNavigationBarColor
Window#setNavigationBarContrastEnforced
文章看到这里,edge-to-edge全面屏体验就已经成功启用了,但是我们还一行代码都没写呢。
所以,适配edge-to-edge真的一行代码都不用写吗?
当然不是,需不需要对edge-to-edge进行额外的适配工作,主要还是取决于你的界面是什么样的。
像刚才的照片墙界面,由于它非常适合填充满手机屏幕的全部空间,即使我们不做任何的适配,用户体验仍然是非常好的。
但是换一个其他的界面就未必如此了。
这里我使用《第一行代码 第3版》第4章的最佳实践项目来作为例子进行演示,看过的读者朋友们应该都知道这是一个聊天框界面。
同样,由于界面编写不是本篇文章的重点,这里我就不把聊天框的源码实现贴出来了。想对源码进行参考的朋友可以去查阅《第一行代码 第3版》,或者同样访问上述GitHub链接即可。
我们来看下聊天框界面在edge-to-edge全面屏体验下的效果是什么样的吧,直接上截图。
可以看到,这次的效果就没有那么理想了。聊天内容进入了状态栏区域,导致部分文字内容和状态栏重叠不易阅读,输入框和发送按钮则进入了导航栏区域,导致输入框和按钮操作可能会受到影响。
这些就是最典型的edge-to-edge全面屏体验所带来的问题,而这也正是我们需要去适配的地方。
适配的代码其实还是比较简单的,主要就是借助ViewCompat.setOnApplyWindowInsetsListener()这个函数,来对某些指定的View进行偏移,保证其不会被系统的状态栏或导航栏遮挡住就可以了。
对应到当前的界面,那就是要让顶部的聊天内容不要进入状态栏区域,底部的输入框和发送按钮不要进入导航栏区域,代码如下所示:
class ChatActivity : AppCompatActivity(), View.OnClickListener {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
...
ViewCompat.setOnApplyWindowInsetsListener(chatRecyclerView) { v, insets ->
val stateBars = insets.getInsets(WindowInsetsCompat.Type.statusBars())
v.setPadding(stateBars.left, stateBars.top, stateBars.right, stateBars.bottom)
insets
}
ViewCompat.setOnApplyWindowInsetsListener(inputTextLayout) { v, insets ->
val navigationBars = insets.getInsets(WindowInsetsCompat.Type.navigationBars())
v.setPadding(navigationBars.left, navigationBars.top, navigationBars.right, navigationBars.bottom)
insets
}
}
}
这里给chatRecyclerView和inputTextLayout两个View设置了Insets监听,chatRecyclerView就是聊天内容对应的View,inputTextLayout就是包含输入框和发送按钮的布局。
那么由于我们不希望聊天内容进入状态栏区域,因此这里调用了WindowInsetsCompat.Type.statusBars()来获取状态栏的Insets,比如说这里获取到状态栏的高度是50,那么我们通过对chatRecyclerView设置一个50的padding就可以保证聊天内容不进入状态栏区域了。
类似地,我们不希望输入框和发送按钮进入导航栏区域,那么就调用WindowInsetsCompat.Type.navigationBars()来获取导航栏的Insets,之后再用同样的方法来设置padding即可。
加上这一段代码之后,重新运行程序就可以得到比较理想的效果了,如下图所示。
除了WindowInsetsCompat.Type.statusBars()和WindowInsetsCompat.Type.navigationBars()之外,其实我们还有许多其他类型的Insets可供选择。
比如,如果你希望某个View,既不进入状态栏区域,也不进入导航栏区域,那么可以使用WindowInsetsCompat.Type.systemBars()。
如果你希望某个View,不会进入到Cutout区域,那么可以使用WindowInsetsCompat.Type.displayCutout()。
Cutout这个概念是Android 9系统时引入的,那个时候手机刚刚兴起了刘海屏,为了能够适配各式各样可能出现的刘海,Google引入了Cutout API。
不过后来手机厂商并没有做出各种奇形怪状的刘海,基本都是选择把刘海做到了状态栏里面,所以现在displayCutout()这个API的效果和statusBars()已经没有太大区别了。
想要对刘海屏有更详细的了解,可以参考这篇文章 Android 9新特性,对刘海屏设备进行适配 。
另外从Android 10开始,Google引入了手势导航,这使得手机屏幕的左右两侧可以用于触发Back键操作,手机屏幕的底部可以用于触发Home键操作,触发区域如下图中的黄色部分所示。
也就是说,如果我们设计的界面在这个区域有正好类似的手势操作,那么就会出现手势冲突的情况,导致用户的操作无法正常进行。
这个时候可以使用WindowInsetsCompat.Type.systemGestures()来获得黄色区域的Insets,然后再通过设置padding的方式让有事件冲突的View偏离这个区域就可以了。
基本上关于edge-to-edge全面屏适配要讲的内容也就是这些了,适配的代码还是比较简单的。但是由于它影响到的是所有的项目,而每个项目的UI界面复杂度都各不相同,因此具体会对大家带来多少影响可能还得要大家自己去评估了。
最后,开篇的时候提到过,edge-to-edge全面屏体验其实并不是全新的功能,在Android 15之前也是支持的,Android 15只是将这个功能强制开启了而已。
那么如果我们已经为edge-to-edge全面屏体验做好了适配,就没有任何理由只在Android 15上启用,当然是启用的设备越多越好。
要在Android 15之前的设备上启用edge-to-edge全面屏体验,只需要额外两步就可以完成。
第一步,在项目的build.gradle文件中添加如下库的依赖:
dependencies {
// For Java
implementation 'androidx.activity:activity:$activity_version'
// For Kotlin
implementation 'androidx.activity:activity-ktx:$activity_version'
}
第二步,在Activity的onCreate函数中添加如下代码:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
enableEdgeToEdge()
super.onCreate(savedInstanceState)
...
}
}
enableEdgeToEdge()是一个Kotlin的扩展函数,用于为旧版Android系统的设备启用edge-to-edge全面屏体验。如果你的项目还在使用Java代码,那么可以使用以下写法替代:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
EdgeToEdge.enable(this);
super.onCreate(savedInstanceState);
...
}
}
完成以上两步之后,edge-to-edge全面屏体验就可以在Android 15之前的设备上启用了,最早可以支持到Android 6.0的设备。
好了,关于edge-to-edge全面屏体验的所有介绍就到这里,我们下篇原创再见。